package com.lagou.edu.factory;

import com.lagou.edu.annotation.Autowired;
import com.lagou.edu.annotation.Service;
import com.lagou.edu.annotation.Transactional;
import com.lagou.edu.utils.ConnectionUtils;
import com.lagou.edu.utils.TransactionManager;

import java.io.File;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

/**
 * @author 应癫
 * <p>
 * 工厂类，生产对象（使用反射技术）
 */
public class BeanFactory {

    /**
     * 任务一：读取解析xml，通过反射技术实例化对象并且存储待用（map集合）
     * 任务二：对外提供获取实例对象的接口（根据id获取）
     */

    private static Map<String, Object> map = new HashMap<>();  // 缓存对象

    private static Map<String, Object> complete = new HashMap<>();  // 存储对象

    // 任务二：对外提供获取实例对象的接口（根据id获取）
    public static Object getBean(String id) {
        return map.get(id);
    }

    public static void init(String url) throws Exception {
        System.out.println("初始化开始" + url);
        System.out.println("存入缓存");
        scanPackage(url);
        System.out.println("注入属性");
        DI();
        System.out.println("代理");
        transactionalProxy();
        System.out.println("注入属性");
        DI();
        System.out.println("初始化完成");
        System.out.println(map);
    }

    private static void transactionalProxy() {
        ProxyFactory proxyFactory = new ProxyFactory(new TransactionManager((ConnectionUtils) BeanFactory.getBean("connectionUtils")));

        Set<Map.Entry<String, Object>> entries = map.entrySet();
        Iterator<Map.Entry<String, Object>> iterator = entries.iterator();
        while (iterator.hasNext()) {
            Map.Entry<String, Object> next = iterator.next();
            String key = next.getKey();
            Object value = next.getValue();
            boolean assignableFrom = value.getClass().isAnnotationPresent(Transactional.class);
            if (assignableFrom) {
                //受事务管理，生产代理对象
                value = proxyFactory.proxy(value);
                //代理对象替换原对象
                map.put(key, value);
            }
        }
    }

    private static void DI() throws Exception {
//        boolean flag = true;
        Set<Map.Entry<String, Object>> entries = map.entrySet();
        Iterator<Map.Entry<String, Object>> iterator = entries.iterator();
        while (iterator.hasNext()) {
            Map.Entry<String, Object> next = iterator.next();
            String key = next.getKey();
            Object value = next.getValue();
            Field[] declaredFields = value.getClass().getDeclaredFields();
            for (int i = 0; i < declaredFields.length; i++) {
                Field declaredField = declaredFields[i];
                boolean annotationPresent = declaredField.isAnnotationPresent(Autowired.class);
                if (annotationPresent) {
                    Autowired annotation = declaredField.getAnnotation(Autowired.class);
                    String name = annotation.name();

                    Object o = getObjectfromMapOrComplete(name, declaredField.getName(), declaredField.getType());

                    if (null == o) {
                        throw new RuntimeException("注入失败");
                    }

                    Method[] methods = value.getClass().getMethods();
                    Class<?> aClass = o.getClass();

                    String methodName = declaredField.getName();
                    methodName = String.valueOf(methodName.charAt(0)).toUpperCase() + methodName.substring(1);
                    Method method = value.getClass().getMethod("set" + methodName, declaredField.getType());
                    method.invoke(value, o);
                }
            }
        }


//        if (flag) {
//            return;
//        } else {
//            DI();
//        }
    }

    private static Object getObjectfromMapOrComplete(String name, String paramName, Class aclass) {
        if (null != name && !"".equals(name)) {
            if (map.containsKey(name)) {
                return map.get(name);
            }

        }
        {
            if (map.containsKey(paramName)) {
                return map.get(paramName);
            }
        }
        {
            Object o = null;
            //类型注入
            Set<Map.Entry<String, Object>> entries = map.entrySet();
            Iterator<Map.Entry<String, Object>> iterator = entries.iterator();
            while (iterator.hasNext()) {
                Map.Entry<String, Object> next = iterator.next();
                Object value = next.getValue();
                Class<?> bClass = value.getClass();
                if (aclass.isAssignableFrom(bClass)) {
                    if (null == o) {
                        o = value;
                    } else {
                        throw new RuntimeException("重复类型");
                    }
                }
            }
            if (null != o) {
                return o;
            }
        }
        throw new RuntimeException("Autowired.name错误");
    }

    private static void scanPackage(final String pkg) {
        System.out.println("当前目录" + pkg);
        String pkgDir = pkg.replaceAll("\\.", "/");
        URL url = BeanFactory.class.getClassLoader().getResource(pkgDir);
        File root = new File(url.getFile());
        File[] fs = root.listFiles((file) -> {
            String fName = file.getName();
            System.out.println("当前文件名" + fName);
            if (file.isDirectory()) {
                scanPackage(pkg + "." + fName);
            } else {
                //取class文件
                if (fName.endsWith(".class")) {
                    return true;
                }
            }
            return false;
        });
        if (null != fs) {
            for (File f : fs) {
                String fName = f.getName();
                //去除.class以后的文件名
                fName = fName.substring(0, fName.lastIndexOf("."));
                //将名字的第一个字母转为小写(用它作为key存储map)
                String key = String.valueOf(fName.charAt(0)).toLowerCase() + fName.substring(1);
                //构建一个类全名(包名.类名)
                String pkgCls = pkg + "." + fName;
                try {
                    //反射构建对象
                    Class<?> c = Class.forName(pkgCls);
                    //判定类上是否有注解isAnnotationPresent()
                    if (c.isAnnotationPresent(Service.class)) {
                        Object obj = c.newInstance();
                        //将对象放到map容器
                        map.put(key, obj);
                    }

                } catch (Exception e) {
                    throw new RuntimeException(e);
                }
            }
        }
        System.out.println("============");
    }
}
